
#define WRITE_DEBUG_INFO_xxx

using System;
using System.Collections.Generic;
using NUnit.Framework;
using System.Text;

namespace ProgrammingLanguageNr1
{
	public class SymbolTableBuilder
	{
		public SymbolTableBuilder (AST ast, ErrorHandler errorHandler)
		{
			Assert.IsNotNull(ast);
			Assert.IsNotNull(errorHandler);
			
			m_ast = ast;
			m_errorHandler = errorHandler;
		}
		
		public void process() {
			m_globalScope = new GlobalScope();
			m_currentScope = m_globalScope;
			
#if DEFINE_BUG_INFO
			Console.WriteLine("Evaluate scope declarations:");
#endif
			evaluateScopeDeclarations(m_ast);
#if DEFINE_BUG_INFO
			Console.WriteLine("Evaluate references:");
#endif
			evaluateReferences(m_ast);
		}
		
		private void evaluateScopeDeclarations(AST tree) {
			Assert.IsNotNull(tree);
			
			if (tree.getTokenType() == Token.TokenType.FUNC_DECLARATION) 
			{
				// Define function name
				SprakType returnType = (SprakType)m_currentScope.resolve(tree.getChild(0).getTokenString());
				string functionName = tree.getChild(1).getTokenString();
				Symbol functionScope = new FunctionSymbol(m_currentScope, functionName, returnType, tree);
				m_globalScope.define(functionScope); // all functions are saved in the global scope
				m_currentScope = (Scope)functionScope;
				AST_FunctionDefinitionNode functionCallNode = (AST_FunctionDefinitionNode)(tree);
				functionCallNode.setScope((Scope)functionScope);
#if DEFINE_BUG_INFO
				Console.WriteLine("Defined function with name " + functionName + " and return type " + returnType);
#endif
				
				// Define parameters
				AST parameterList = tree.getChild(2);
				if(parameterList.getChildren() != null) {
					foreach(AST parameterTree in parameterList.getChildren()) {
						SprakType parameterTypeToDeclare = (SprakType)m_currentScope.resolve(parameterTree.getChild(0).getTokenString());
						string parameterName = parameterTree.getChild(1).getTokenString();
						m_currentScope.define(new VariableSymbol(parameterName, parameterTypeToDeclare));
#if DEFINE_BUG_INFO
						Console.WriteLine("Defined parameter with name " + parameterName + " and type " + parameterTypeToDeclare);
#endif
					}
				}
				
				// Process the body of the function
				evaluateScopeDeclarations(tree.getChild(3));
				
				m_currentScope = m_currentScope.getEnclosingScope(); // pop scope
			}
			else if (tree.getTokenType() == Token.TokenType.IF) {			
				LocalScope subscope = new LocalScope(m_currentScope, "<IF-SUBSCOPE>");
#if DEFINE_BUG_INFO
				Console.WriteLine("Defined IF-subscope");
#endif
				m_currentScope = (Scope)subscope;
				AST_IfNode ifNode = (AST_IfNode)(tree);
				ifNode.setScope((Scope)subscope); // save the new scope in the IF-token tree node
				evaluateFunctionDeclarationsInAllChildren(tree);
				m_currentScope = m_currentScope.getEnclosingScope(); // pop scope
			}
			else if (tree.getChildren() != null) 
			{
				evaluateFunctionDeclarationsInAllChildren(tree);
			}
		}
		
		private void evaluateFunctionDeclarationsInAllChildren(AST tree) {
			foreach ( AST subtree in tree.getChildren() ) {
				evaluateScopeDeclarations(subtree);
			}	
		}
		
		private void evaluateReferences(AST tree) {
			Assert.IsNotNull(tree);
			
			if (tree.getTokenType() == Token.TokenType.VAR_DECLARATION) 
			{
				string nameOfTypeToDeclare = tree.getChild(0).getTokenString();
				SprakType typeToDeclare = (SprakType)m_currentScope.resolve(nameOfTypeToDeclare);
				if(typeToDeclare == null) {
					m_errorHandler.errorOccured(
						"Can't find type with name " + nameOfTypeToDeclare, 
						Error.ErrorType.SYNTAX,
						tree.getToken().LineNr,
						tree.getToken().LinePosition);
				}
				else {
					string variableName = tree.getChild(1).getTokenString();
				
					if(m_currentScope.isDefined(variableName)) 
					{
						m_errorHandler.errorOccured(
							new Error("Trying to redefine symbol with name " + variableName, 
							Error.ErrorType.LOGIC, 
							tree.getToken().LineNr,
							tree.getToken().LinePosition));
					} else {
						m_currentScope.define(new VariableSymbol(variableName, typeToDeclare));
					}
					
					
#if DEFINE_BUG_INFO
					Console.WriteLine("Defined variable with name " + variableName + " and type " + typeToDeclare);
#endif
				}
			}
			else if (tree.getTokenType() == Token.TokenType.FUNC_DECLARATION) 
			{
				string functionName = tree.getChild(1).getTokenString();
				m_currentScope = (Scope)m_currentScope.resolve(functionName); // push the scope with the function
#if DEFINE_BUG_INFO
				Console.WriteLine("Resolved function parameters and body of " + functionName);
#endif
				evaluateReferencesInAllChildren(tree.getChild(2)); // parameters
				evaluateReferencesInAllChildren(tree.getChild(3)); // function body
				m_currentScope = m_currentScope.getEnclosingScope(); // pop scope
			}
			else if (tree.getTokenType() == Token.TokenType.FUNCTION_CALL) 
			{
				// Function name:
				string functionName = tree.getTokenString();
				FunctionSymbol function = (FunctionSymbol)m_currentScope.resolve(functionName);
				if(function == null) {
					m_errorHandler.errorOccured("Can't find function with name " + functionName, Error.ErrorType.LOGIC);
				}
				else {
#if DEFINE_BUG_INFO
					Console.WriteLine("Resolved function with name " + functionName);
#endif
					
					// Parameters
					evaluateReferencesInAllChildren(tree);
				
					AST node = function.getFunctionDefinitionNode();
					AST_FunctionDefinitionNode functionDefinitionTree = (AST_FunctionDefinitionNode)(node);
					List<AST> parameterList = functionDefinitionTree.getChild(2).getChildren();
					
					// Check that the number of arguments is right
					List<AST> arguments = tree.getChildren();
					
					if(arguments.Count != parameterList.Count) {
						m_errorHandler.errorOccured(
							"Wrong number of arguments to function"
							, Error.ErrorType.SYNTAX, tree.getToken().LineNr, tree.getToken().LinePosition);
					}
				}
			}
			else if (tree.getTokenType() == Token.TokenType.IF) 
			{
				AST_IfNode ifNode = (AST_IfNode)(tree);
				m_currentScope = (Scope)ifNode.getScope(); // push IF-subscope
				evaluateReferencesInAllChildren(tree);
				m_currentScope = m_currentScope.getEnclosingScope(); // pop scope
			}
			else if (tree.getTokenType() == Token.TokenType.NAME) 
			{
				m_currentScope.resolve(tree.getTokenString());
#if DEFINE_BUG_INFO
				Console.WriteLine("Resolved symbol with name " + tree.getTokenString() + " (on line " + tree.getToken().LineNr + ")");
#endif
				evaluateReferencesInAllChildren(tree);
			}
			else 
			{
				evaluateReferencesInAllChildren(tree);
			}
		}
		
		private void evaluateReferencesInAllChildren(AST tree) {
			if (tree.getChildren() != null) {
				// Go through all other subtrees
				foreach (AST subtree in tree.getChildren()) {
					evaluateReferences(subtree);
				}
			}
		}
		
		public GlobalScope getGlobalScope() {
			Assert.IsNotNull(m_globalScope, "The global scope is null, this probably means that you haven't called process() on SymbolTableBuilder");
			return m_globalScope;
		}
		
		AST m_ast;
		GlobalScope m_globalScope;
		Scope m_currentScope;
		ErrorHandler m_errorHandler;
	}
}

